package com.chenjia.proto.converter.utils;

import com.chenjia.proto.converter.annotation.ProtoClass;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Map;

/**
 * @author jsjem
 * @author Roman Gushel
 */
public final class FieldUtils {

    private static final String HASSER_PREFIX = "has";
    private static final String GETTER_PREFIX = "get";
    private static final String SETTER_PREFIX = "set";
    private static final String BOOLEAN_GETTER_PREFIX = "is";
    private static final String PROTOBUF_LIST_GETTER_POSTFIX = "List";
    private static final String PROTOBUF_LIST_SETTER_PREFIX = "addAll";

    private static final String PROTOBUF_MAP_SETTER_PREFIX = "putAll";
    private static final String PROTOBUF_MAP_GETTER_POSTFIX = "Map";

    /**
     * Check whether field has own mapper.
     *
     * @param field Testing field.
     * @return true if field type has {@link ProtoClass} annotation.
     */
    public static boolean isComplexType(final Field field) {
        return isComplexType(field.getType());
    }

    /**
     * Check whether type has own mapper.
     *
     * @param type Testing type.
     * @return true if field type has {@link ProtoClass} or {@link ProtoClasses} annotation.
     */
    public static boolean isComplexType(final Class<?> type) {
        return type.isAnnotationPresent(ProtoClass.class);
    }

    /**
     * Check whether field type implements Collection interface.
     *
     * @param field Testing field.
     * @return true if field type implements {@link Collection}, otherwise false.
     */
    public static boolean isCollectionType(final Field field) {
        return isCollectionType(field.getType());
    }

    /**
     * Check whether class implements Collection interface.
     *
     * @param type Testing class.
     * @return true if class implements {@link Collection}, otherwise false.
     */
    public static boolean isCollectionType(final Class<?> type) {
        return Collection.class.isAssignableFrom(type);
    }

    public static boolean isMapType(final Class<?> type) {
        return Map.class.isAssignableFrom(type);
    }

    /**
     * Create protobuf getter name for domain field.
     */
    public static String createProtobufHasserName(final Class<?> messageType, final String name) {
        if (isCollectionType(messageType)) {
            return null;
        }
        return StringUtils.createMethodName(HASSER_PREFIX, name);
    }

    /**
     * Create protobuf getter name for domain field.
     */
    public static String createProtobufGetterName(final String name, Class type) {
        String getterName = StringUtils.createMethodName(GETTER_PREFIX, name);
        if (isCollectionType(type)) {
            return getterName + PROTOBUF_LIST_GETTER_POSTFIX;
        } else if (isMapType(type)) {
            return getterName + PROTOBUF_MAP_GETTER_POSTFIX;
        }
        return getterName;
    }

    /**
     * Create protobuf setter name for domain field.
     */
    public static String createProtobufSetterName(final Class<?> messageBuilderType, final String name) {
        if (isCollectionType(messageBuilderType)) {
            return StringUtils.createMethodName(PROTOBUF_LIST_SETTER_PREFIX, name);
        } else if (isMapType(messageBuilderType)) {
            return StringUtils.createMethodName(PROTOBUF_MAP_SETTER_PREFIX, name);
        }
        return StringUtils.createMethodName(SETTER_PREFIX, name);
    }

    /**
     * Create domain field getter name.
     */
    public static String createDomainGetterName(final Class<?> type, final String name) {
        if (type == boolean.class) {
            return StringUtils.createMethodName(BOOLEAN_GETTER_PREFIX, name);
        }
        return StringUtils.createMethodName(GETTER_PREFIX, name);
    }

    /**
     * Create domain field setter name.
     */
    public static String createDomainSetterName(final String name) {
        return StringUtils.createMethodName(SETTER_PREFIX, name);
    }

    /**
     * Extract parameter type of the collection.
     *
     * @param field Field with type derived from {@link Collection}.
     * @return Collection generic type.
     */
    public static Class<?> extractCollectionType(final Field field) {
        ParameterizedType genericType = (ParameterizedType) field.getGenericType();
        return (Class<?>) genericType.getActualTypeArguments()[0];
    }

    /**
     * Extract protobuf field type from type converter.
     *
     * @return Protobuf field type declared in the type converter class or default type when it is unable to extract
     * field type from converter.
     */
//    public static Class<?> extractProtobufFieldType(final Class<? extends TypeConverter> typeConverterClass,
//                                                    final Class<?> defaultType) {
//        Type[] interfaceTypes = typeConverterClass.getGenericInterfaces();
//        for (Type interfaceType : interfaceTypes) {
//            ParameterizedType parameterizedType = (ParameterizedType) interfaceType;
//            if (parameterizedType.getRawType().equals(TypeConverter.class)) {
//                Type extractedType = parameterizedType.getActualTypeArguments()[1];
//                if (extractedType instanceof ParameterizedType) {
//                    return (Class<?>) ((ParameterizedType) extractedType).getRawType();
//                }
//                return Object.class.equals(extractedType) ? defaultType : (Class<?>) extractedType;
//            }
//        }
//        return defaultType;
//    }
    private FieldUtils() {
        // empty
    }
}
